{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# JupyterDash\n",
    "The `jupyter-dash` package makes it easy to develop Plotly Dash apps from the Jupyter Notebook and JupyterLab.\n",
    "\n",
    "Just replace the standard `dash.Dash` class with the `jupyter_dash.JupyterDash` subclass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jupyter_dash import JupyterDash"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dash\n",
    "from dash import dcc\n",
    "from dash import html\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When running in JupyterHub or Binder, call the `infer_jupyter_config` function to detect the proxy configuration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "JupyterDash.infer_jupyter_proxy_config()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Load and preprocess data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')\n",
    "available_indicators = df['Indicator Name'].unique()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Construct the app and callbacks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']\n",
    "\n",
    "app = JupyterDash(__name__, external_stylesheets=external_stylesheets)\n",
    "\n",
    "# Create server variable with Flask server object for use with gunicorn\n",
    "server = app.server\n",
    "\n",
    "app.layout = html.Div([\n",
    "    html.Div([\n",
    "\n",
    "        html.Div([\n",
    "            dcc.Dropdown(\n",
    "                id='crossfilter-xaxis-column',\n",
    "                options=[{'label': i, 'value': i} for i in available_indicators],\n",
    "                value='Fertility rate, total (births per woman)'\n",
    "            ),\n",
    "            dcc.RadioItems(\n",
    "                id='crossfilter-xaxis-type',\n",
    "                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],\n",
    "                value='Linear',\n",
    "                labelStyle={'display': 'inline-block'}\n",
    "            )\n",
    "        ],\n",
    "        style={'width': '49%', 'display': 'inline-block'}),\n",
    "\n",
    "        html.Div([\n",
    "            dcc.Dropdown(\n",
    "                id='crossfilter-yaxis-column',\n",
    "                options=[{'label': i, 'value': i} for i in available_indicators],\n",
    "                value='Life expectancy at birth, total (years)'\n",
    "            ),\n",
    "            dcc.RadioItems(\n",
    "                id='crossfilter-yaxis-type',\n",
    "                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],\n",
    "                value='Linear',\n",
    "                labelStyle={'display': 'inline-block'}\n",
    "            )\n",
    "        ], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})\n",
    "    ], style={\n",
    "        'borderBottom': 'thin lightgrey solid',\n",
    "        'backgroundColor': 'rgb(250, 250, 250)',\n",
    "        'padding': '10px 5px'\n",
    "    }),\n",
    "\n",
    "    html.Div([\n",
    "        dcc.Graph(\n",
    "            id='crossfilter-indicator-scatter',\n",
    "            hoverData={'points': [{'customdata': 'Japan'}]}\n",
    "        )\n",
    "    ], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),\n",
    "    html.Div([\n",
    "        dcc.Graph(id='x-time-series'),\n",
    "        dcc.Graph(id='y-time-series'),\n",
    "    ], style={'display': 'inline-block', 'width': '49%'}),\n",
    "\n",
    "    html.Div(dcc.Slider(\n",
    "        id='crossfilter-year--slider',\n",
    "        min=df['Year'].min(),\n",
    "        max=df['Year'].max(),\n",
    "        value=df['Year'].max(),\n",
    "        marks={str(year): str(year) for year in df['Year'].unique()},\n",
    "        step=None\n",
    "    ), style={'width': '49%', 'padding': '0px 20px 20px 20px'})\n",
    "])\n",
    "\n",
    "\n",
    "@app.callback(\n",
    "    dash.dependencies.Output('crossfilter-indicator-scatter', 'figure'),\n",
    "    [dash.dependencies.Input('crossfilter-xaxis-column', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-xaxis-type', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-yaxis-type', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-year--slider', 'value')])\n",
    "def update_graph(xaxis_column_name, yaxis_column_name,\n",
    "                 xaxis_type, yaxis_type,\n",
    "                 year_value):\n",
    "    dff = df[df['Year'] == year_value]\n",
    "\n",
    "    return {\n",
    "        'data': [dict(\n",
    "            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],\n",
    "            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],\n",
    "            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],\n",
    "            customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],\n",
    "            mode='markers',\n",
    "            marker={\n",
    "                'size': 25,\n",
    "                'opacity': 0.7,\n",
    "                'color': 'orange',\n",
    "                'line': {'width': 2, 'color': 'purple'}\n",
    "            }\n",
    "        )],\n",
    "        'layout': dict(\n",
    "            xaxis={\n",
    "                'title': xaxis_column_name,\n",
    "                'type': 'linear' if xaxis_type == 'Linear' else 'log'\n",
    "            },\n",
    "            yaxis={\n",
    "                'title': yaxis_column_name,\n",
    "                'type': 'linear' if yaxis_type == 'Linear' else 'log'\n",
    "            },\n",
    "            margin={'l': 40, 'b': 30, 't': 10, 'r': 0},\n",
    "            height=450,\n",
    "            hovermode='closest'\n",
    "        )\n",
    "    }\n",
    "\n",
    "\n",
    "def create_time_series(dff, axis_type, title):\n",
    "    return {\n",
    "        'data': [dict(\n",
    "            x=dff['Year'],\n",
    "            y=dff['Value'],\n",
    "            mode='lines+markers'\n",
    "        )],\n",
    "        'layout': {\n",
    "            'height': 225,\n",
    "            'margin': {'l': 20, 'b': 30, 'r': 10, 't': 10},\n",
    "            'annotations': [{\n",
    "                'x': 0, 'y': 0.85, 'xanchor': 'left', 'yanchor': 'bottom',\n",
    "                'xref': 'paper', 'yref': 'paper', 'showarrow': False,\n",
    "                'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',\n",
    "                'text': title\n",
    "            }],\n",
    "            'yaxis': {'type': 'linear' if axis_type == 'Linear' else 'log'},\n",
    "            'xaxis': {'showgrid': False}\n",
    "        }\n",
    "    }\n",
    "\n",
    "\n",
    "@app.callback(\n",
    "    dash.dependencies.Output('x-time-series', 'figure'),\n",
    "    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),\n",
    "     dash.dependencies.Input('crossfilter-xaxis-column', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-xaxis-type', 'value')])\n",
    "def update_y_timeseries(hoverData, xaxis_column_name, axis_type):\n",
    "    country_name = hoverData['points'][0]['customdata']\n",
    "    dff = df[df['Country Name'] == country_name]\n",
    "    dff = dff[dff['Indicator Name'] == xaxis_column_name]\n",
    "    title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)\n",
    "    return create_time_series(dff, axis_type, title)\n",
    "\n",
    "\n",
    "@app.callback(\n",
    "    dash.dependencies.Output('y-time-series', 'figure'),\n",
    "    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),\n",
    "     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),\n",
    "     dash.dependencies.Input('crossfilter-yaxis-type', 'value')])\n",
    "def update_x_timeseries(hoverData, yaxis_column_name, axis_type):\n",
    "    dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]\n",
    "    dff = dff[dff['Indicator Name'] == yaxis_column_name]\n",
    "    return create_time_series(dff, axis_type, yaxis_column_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Serve the app using `run_server`.  Unlike the standard `Dash.run_server` method, the `JupyterDash.run_server` method doesn't block execution of the notebook. It serves the app in a background thread, making it possible to run other notebook calculations while the app is running.\n",
    "\n",
    "This makes it possible to iteratively update the app without rerunning the potentially expensive data processing steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.run_server()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By default, `run_server` displays a URL that you can click on to open the app in a browser tab. The `mode` argument to `run_server` can be used to change this behavior.  Setting `mode=\"inline\"` will display the app directly in the notebook output cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.run_server(mode=\"inline\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When running in JupyterLab, with the `jupyterlab-dash` extension, setting `mode=\"jupyterlab\"` will open the app in a tab in JupyterLab.\n",
    "\n",
    "```python\n",
    "app.run_server(mode=\"jupyterlab\")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "formats": "ipynb,py:percent"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
